import { Response } from "qqlx-core";

import { checkResponse } from "@lib/com.response";

type DTO = null | Record<string, any>;

export class CommonRequest {
  private HOST = "";
  defaultHeaders: Record<string, string> = {};

  constructor() {}

  async get<Q extends DTO, R extends DTO>(path: string, query?: Q) {
    const config = { headers: { ...this.defaultHeaders } };
    if (query) {
      const query_str = new URLSearchParams(query).toString();
      const response = await fetch(`${this.HOST}${path}?${query_str}`, config);
      return this.getResponse<R>(response);
    } else {
      const response = await fetch(`${this.HOST}${path}`, config);
      return this.getResponse<R>(response);
    }
  }

  async post<Q extends DTO, R>(path: string, dto?: Q) {
    return this._fetch<R>("POST", path, { dto });
  }

  async put<Q extends DTO, R>(path: string, dto?: Q) {
    return this._fetch<R>("PUT", path, { dto });
  }

  async getByPut<Q extends DTO, R>(path: string, dto?: Q) {
    return this._fetch<R>("PUT", path + "/get", { dto });
  }

  async patch<Q extends DTO, R>(path: string, dto?: Q) {
    return this._fetch<R>("PATCH", path, { dto });
  }

  async delete<Q extends DTO, R>(path: string, dto?: Q) {
    return this._fetch<R>("DELETE", path, { dto });
  }

  private async _fetch<R>(method: string, path: string, body?: DTO): Promise<R> {
    const response = await fetch(`${this.HOST}${path}`, {
      method,
      headers: { ...this.defaultHeaders, "Content-Type": "application/json" },
      body: body ? JSON.stringify(body) : null,
    });
    return this.getResponse<R>(response);
  }

  async formData<R>(path: string, form: FormData) {
    const response = await fetch(`${this.HOST}${path}`, {
      method: "POST",
      // 注意 Content-Type 在node环境中无法取得 boundary 导致500问题
      // https://stackoverflow.com/questions/39280438/fetch-missing-boundary-in-multipart-form-data-post
      headers: { ...this.defaultHeaders },
      body: form,
    });
    return this.getResponse<R>(response);
  }

  private async getResponse<R>(response: globalThis.Response) {
    // HTTP 状态码
    if (response.ok) {
      const contentType = response.headers.get("Content-Type");

      // JSON 数据
      if (contentType && contentType.includes("application/json")) {
        const info: Response<R> = await response.json();
        return checkResponse<R>(info);
      }
      // 非JSON
      else {
        const text = await response.text();
        throw { message: `http status ${response.status}: ${text}: ${response.url}` };
      }
    }

    // HTTP 异常状态码
    else {
      throw { message: `http status ${response.status}: ${response.statusText}: ${response.url}` };
    }
  }

  setDefaultHeaders(key: string, value: string) {
    this.defaultHeaders[key] = value;
  }

  setHost(host: string) {
    this.HOST = host;
  }
}

/** 进程唯一默认请求体 */
export const getRequestInstance = () => new CommonRequest();
export const $request = getRequestInstance();
